/**
 * Created By xiyuan
 * Author server@xiyuan.name (惜缘叛逆)
 * DateTime 2019/3/12 6:18 PM
 * Describe javascript功能描述
 * MIT License http://www.opensource.org/licenses/mit-license.php
 */

// 文件工具
const fs = require('fs');
// 路径包
const path = require('path');
// ini配置文件处理工具
const ini = require('ini');
// api处理工具
const {clientApi} = require('./apiTools')
// 配置转换
const {optionConvert} = require('./optionConvert');
// 进程管理
const {createProcess, getProcess} = require('./processMange');
// 工具集
const {dirExists} = require('./utils');

// 项目根目录
const projectDir = path.join(__dirname, '/../');

// 配置文件目录
const configDir = dirExists(path.normalize(projectDir + 'config/client/'));

// 日志目录
const logDir = dirExists(path.normalize(projectDir + 'log/client/'));

// 配置文件路径
const configPath = path.normalize(configDir + 'client.json');

// 国际化
const i18n = {
	zh: {
		"server_addr": "服务器地址",
		"server_port": "服务器端口",
		"log_file": "日志文件路径",
		"log_level": "日志级别",
		"log_max_days": "日志保存天数",
		"token": "令牌",
		"admin_addr": "管理端地址",
		"admin_port": "管理端端口",
		"admin_user": "管理员名称",
		"admin_pwd": "管理员密码",
		"pool_count": "连接池数量",
		"tcp_mux": "TCP多路复用",
		"user": "用户名称",
		// "login_fail_exit": "登录失败时是否退出程序",
		"login_fail_exit": "登录失败退出",
		"protocol": "协议",
		"type": "协议类型",
		"local_ip": "本地IP",
		"local_port": "本地端口",
		"use_encryption": "启用加密",
		"use_compression": "启用压缩",
		"remote_port": "远程端口",
		"group": "代理组",
		"group_key": "组键值",
		// "health_check_type": "启用健康检查功能协议类型",
		"health_check_type": "健康检查类型",
		"health_check_timeout_s": "建立连接超时时间",
		// "health_check_max_failed": "连续检查N次失败，此代理会被移除",
		"health_check_max_failed": "最大健康检查/次",
		// "health_check_interval_s": "每隔N秒进行一次健康检查",
		"health_check_interval_s": "健康检查/秒",
		"http_user": "web账号",
		"http_pwd": "web密码",
		"subdomain": "二级域名",
		"custom_domains": "自定义二级域名",
		"locations": "URL路由",
		"host_header_rewrite": "Host重写",
		// "header_X-From-Where": "重写前缀header_的请求头",
		"header_X-From-Where": "header_前缀",
		// "health_check_url": "健康检查发送 http 请求的 url",
		"health_check_url": "健康检查url",
		"plugin": "代理插件",
		"plugin_unix_path": "Unix域套接字路径",
		"plugin_http_user": "文件服务账号",
		"plugin_http_passwd": "文件服务密码",
		"plugin_user": "socks5用户名",
		"plugin_passwd": "socks5密码",
		// "plugin_local_path": "文件服务器本地资源路径",
		"plugin_local_path": "本地资源路径",
		// "plugin_strip_prefix": "访问 url 中会被去除的前缀，保留的内容即为要访问的文件路径",
		"plugin_strip_prefix": "url前缀",
		"sk": "密钥",
		"role": "角色",
		"server_name": "要访问的代理的名字",
		"bind_addr": "绑定本地地址",
		"bind_port": "绑定本地端口"
	}
}


// 客户端公共配置规则
const commonRules = {
	// 服务器地址
	"server_addr": {
		required: true,
		type: 'ip',
	},
	// 服务器端口
	"server_port": {
		required: true,
		type: 'port',
		value: 7000,
	},
	// 日志文件
	"log_file": {
		value: function (name) {
			return logDir + name + '.log'
		},
		type: 'path',
	},
	// 日志提示等级
	"log_level": {
		value: "info",
		type: "hidden"
	},
	// 日志保存天数
	"log_max_days": {
		type: "number",
		value: "3",
	},
	// 令牌标示
	"token": {
		type: 'text',
	},
	// 管理地址
	"admin_addr": {
		value: "127.0.0.1",
		type: "hidden"
	},
	// 管理端口
	"admin_port": {
		type: 'port',
	},
	// 管理用户名
	"admin_user": {
		type: 'text',
		value: "admin"
	},
	// 管理密码
	"admin_pwd": {
		type: 'text',
		value: "admin"
	},
	// 连接池数量
	"pool_count": {
		type: "number",
		value: "3",
	},
	// TCP 多路复用
	"tcp_mux": {
		value: true,
		type: 'switch',
	},
	// your proxy name will be changed to {user}.{proxy}
	"user": {
		type: 'hidden'
	},
	// 确定第一次登录失败时是否退出程序，否则继续重新登录到FRPS
	"login_fail_exit": {
		value: true,
		type: 'switch',
	},
	// 指定需要使用的协议类型，目前只支持 tcp 和 kcp
	"protocol": {
		value: 'tcp',
		type: 'select',
		option: [
			'tcp',
			'kcp'
		]
	}
}

// 代理类型规则
const proxyTypeRules = {
	// 通过 ssh 访问内网机器
	ssh: {
		'type': {
			value: 'tcp',
			type: 'hidden',
		},
		'local_ip': {
			required: true,
			value: '127.0.0.1',
			type: 'ip',
		},
		'local_port': {
			required: true,
			type: 'port',
		},
		'remote_port': {
			required: true,
			type: 'port',
		},
		"use_encryption": {
			value: false,
			type: 'switch',
		},
		"use_compression": {
			value: false,
			type: 'switch',
		},
	},
	// 转发 DNS 查询请求
	dns: {
		'type': {
			value: 'udp',
			type: 'hidden',
		},
		'local_ip': {
			required: true,
			type: 'ip',
		},
		'local_port': {
			required: true,
			type: 'port',
		},
		'remote_port': {
			required: true,
			type: 'port',
		}
	},
	// 静态服务器
	static: {
		'type': {
			value: 'tcp',
			type: 'hidden',
		},
		'remote_port': {
			required: true,
			type: 'port',
		},
		'plugin': {
			value: 'static_file',
			type: 'hidden',
		},
		'plugin_local_path': {
			required: true,
			type: 'path',
		},
		'plugin_strip_prefix': {
			type: 'path',
		},
		'plugin_http_user': {
			type: 'text',
		},
		'plugin_http_passwd': {
			type: 'password',
			required: 'plugin_http_user'
		},
	},
	// web代理
	web: {
		"type": {
			value: "http",
			type: 'select',
			option: [
				'http',
				'https',
			]
		},
		"local_ip": {
			value: "127.0.0.1",
			required: true,
			type: 'ip',
		},
		"local_port": {
			value: "80",
			required: true,
			type: 'port',
		},
		"use_encryption": {
			value: false,
			type: 'switch',
		},
		"use_compression": {
			value: false,
			type: 'switch',
		},
		"http_user": {
			type: 'text',
		},
		"http_pwd": {
			type: 'password',
			required: 'http_user'
		},
		"subdomain": {
			type: /\w+/,
		},
		"custom_domains": {
			type: 'host',
			required: true
		},
		"locations": {
			type: 'list',
			list: 'path'
		},
		"host_header_rewrite": {
			type: 'host'
		},
		"header_X-From-Where": "frp",
		"health_check_type": {
			type: 'select',
			option: [
				'tcp',
				'udp',
			]
		},
		"health_check_url": {
			type: 'url',
			required: 'health_check_type'
		},
		"health_check_interval_s": {
			type: 'number',
			value: 10,
			required: 'health_check_type'
		},
		"health_check_max_failed": {
			type: 'number',
			value: 10,
			required: 'health_check_type'
		},
		"health_check_timeout_s": {
			type: 'number',
			value: 10,
			required: 'health_check_type'
		}
	},
	// 端口代理
	port: {
		"#": {
			type: 'range',
			field: 'range'
		},
		'type': {
			value: 'tcp',
			type: 'select',
			option: [
				'tcp',
				'udp',
				'xtcp',
				'stcp'
			]
		},
		'range': {
			value: false,
			type: 'switch',
		},
		'local_ip': {
			required: true,
			value: '127.0.0.1',
			type: 'ip',
		},
		'local_port': {
			isRange: 'range',
			required: true,
			type: 'list_port',
			portMap: 'remote_port'
		},
		'remote_port': {
			isRange: 'range',
			required: true,
			type: 'list_port',
			// portMap: 'local_port'
		},
		"use_encryption": {
			value: false,
			type: 'switch',
		},
		"use_compression": {
			value: false,
			type: 'switch',
		},
	},
	// p2p点对点网穿透
	p2p: {
		"type": {
			value: 'xtcp',
			type: 'select',
			option: [
				'xtcp',
				'stcp'
			]
		},
		"sk": {
			type: 'text',
		},
		"role": {
			type: 'text',
		},
		"local_ip": {
			value: "127.0.0.1",
			required: true,
			type: 'ip',
			ignore: 'role'
		},
		"local_port": {
			value: "80",
			required: true,
			type: 'port',
			ignore: 'role'
		},
		"use_encryption": {
			value: false,
			type: 'switch',
		},
		"use_compression": {
			value: false,
			type: 'switch',
		},
		"bind_addr": {
			required: 'role',
			value: "127.0.0.1",
			type: 'ip',
		},
		"bind_port": {
			required: 'role',
			type: 'port',
		}
	},
	// tcp负载均衡
	tcp_balanced: {
		"#": {
			type: 'split',
			field: 'local_port'
		},
		'type': {
			value: 'tcp',
			type: 'hidden',
		},
		'local_port': {
			required: true,
			type: 'list_port',
		},
		'remote_port': {
			required: true,
			type: 'port',
		},
		'group': {
			type: /\w+/,
			required: true,
			randomValue: true,
		},
		'group_key': {
			type: /\w+/,
			required: true,
			randomValue: true,
		},
	}
}

// 客户端公共数据
const clientData = {
	// 客户端容器
	container: [],
	// 累计客户端数量
	count: 0,
	// 进程容器
};

// 添加客户端
function addClient(client) {
	const {config, state} = readConfig();
	// 检查是否有同名服务
	const findIndex = state ? config.container.findIndex(info => info.name == client.name) : -1;
	if (findIndex !== -1) {
		clientData.container.splice(findIndex, 1);
	}
	clientData.container.push(client);
}

// 移除客户端
function removeClient(clientName) {
	// 获取对应的客户端实例中
	const clientInstance = clientName instanceof client ? clientName : clientData.container.find(client => client.name == clientName);
	// return
	if (clientInstance) {
		let index = clientData.container.findIndex(client => client === clientInstance);
		if (index !== -1) {
			// 关闭进程
			clientInstance.closeProcess();
			// 容器中移除
			clientData.container.splice(index, 1);
			// 同步配置
			syncWriteConfig(clientInstance, 'remove');
		}
	}
}

// 配置同步写入
function syncWriteConfig(targetClient, formatting) {
	// 读取配置
	const {config, state} = readConfig();
	const container = clientData.container.concat();
	// 数据合并
	if (state && formatting !== 'remove') {
		config.container.forEach(function (info) {
			if (!container.some(serverInfo => serverInfo.name == info.name)) {
				container.unshift(info);
			}
		})
	}
	
	// 写入保存所有客户端数据
	fs.writeFileSync(configPath, JSON.stringify({
		count: container.length,
		container: container
	}));
	
	// 检查是否要格式化对应客户端配置
	if (!formatting) return;
	const clientName = targetClient.name;
	// 进程配置路径
	const processconfigPath = configDir + clientName + '.ini';
	
	// 检查是否移除配置
	if (formatting === 'remove') {
		return fs.unlinkSync(processconfigPath)
	}
	
	// 检查客户端中是否还存在开启的代理
	const proxy_config = targetClient.proxy_config;
	// 获取还开启的代理
	const clientConfig = Object.keys(proxy_config).reduce((map, key) => {
		return proxy_config[key].reduce((map, info) => {
			if (info.isUse) {
				return info.nameList.reduce((map, name, index) => {
					map[name] = info.configList[index];
					return map;
				}, map)
			}
			return map;
		}, map)
	}, {});
	
	// 公共配置
	const commonConfig = targetClient.common.configList[0]
	
	// 配置文件内容
	const iniText = ini.stringify(
		Object.assign(
			{
				common: commonConfig
			},
			clientConfig
		)
	);
	
	// 保存并写入本地
	fs.writeFileSync(
		processconfigPath,
		iniText
	);
	
	// 检查是否需要启动
	return {
		iniText,
		commonConfig,
		clientConfig,
	};
}

// 配置同步读取
function readConfig() {
	let resString = '[]';
	// 捕获错误信息
	try {
		// 读取配置文件内容
		resString = fs.readFileSync(configPath, 'utf-8')
	}
	catch (e) {
		return {
			state: false,
			errMsg: e.message
		};
	}
	
	return {
		state: true,
		config: JSON.parse(resString)
	};
}


// 客户端实例回复
function clientRecover() {
	// 配置读取
	const {config, state} = readConfig();
	if (!state) return state;
	
	// 实例恢复
	config.container.map(config => {
		return new client(config);
	})
	
	return {
		start() {
			clientData.container.forEach(client => client.startProcess());
			return clientData.container;
		}
	}
	
}

// 客户端类
class client {
	constructor({name, common, optionNames, logPath, options, proxy_config, isUse} = {},) {
		// 事件记录器
		this.eventMap = {
			start: [],
			info: [],
			error: [],
			close: [],
		};
		// 客户端名称
		this.name = name || ('客户端' + clientData.count)
		// 是否启用
		this.isUse = isUse || false;
		// 公共配置
		this.common = common ? common : optionConvert('common', commonRules, options, this);
		// 配置名称存储容器
		this.optionNames = optionNames || [];
		// 初始化代理配置
		this.proxy_config = proxy_config || Object.keys(proxyTypeRules).reduce(function (config, key) {
			config[key] = [];
			return config;
		}, {})
		
		// 检查客户端公共配置是否配置成功
		if (this.common.errMsg) {
			throw new Error(this.common.errMsg)
		} else {
			// 日志路径
			this.logPath = this.common.configList[0].log_file = logPath || this.common.configList[0].log_file.split(path.sep).join('/');
		}
		// 存储客户端实例
		addClient(this);
		// 数据同步
		syncWriteConfig(this, true);
	}
	
	// 新增代理连接
	add(type, name, options, updateProxyConf) {
		// 对应代理类型规则
		const rules = proxyTypeRules[type];
		// 配置转换
		const res = optionConvert(name, rules, options);
		
		let {errMsg, configList, newName, ports} = res;
		
		// 检查配置是否有误
		if (!errMsg) {
			let nameList = [];
			let len = configList.length;
			// 检查配置是否多个
			if (len > 1) {
				while (len) {
					let _name = newName + '[' + len + ']';
					// 收集代理名称
					nameList.push(_name);
					// 检查是否更新的代理
					if (!updateProxyConf || updateProxyConf.nameList.indexOf(_name) === -1) {
						if (this.optionNames.indexOf(_name) !== -1) {
							errMsg = '定义的属性名称冲突-'
						}
					}
					len--;
				}
			} else {
				// 收集代理名称
				nameList.push(newName);
				// 检查是否更新的代理
				if (!updateProxyConf || updateProxyConf.nameList.indexOf(newName) === -1) {
					if (this.optionNames.indexOf(newName) !== -1) {
						errMsg = '定义的属性名称冲突'
					}
				}
			}
			
			// 配置无问题（进行配置存储）
			if (!errMsg) {
				const res = {
					ports,
					// 是否开启
					isUse: false,
					// 配置名称
					name,
					// 代理配置真实名称
					nameList,
					// 原始配置
					options,
					// 代理工具可使用的配置
					configList,
				}
				
				if (updateProxyConf) {
					// 移除老数据的代理名称
					updateProxyConf.nameList.forEach(name => {
						let index = this.optionNames.findIndex(itemName => itemName === name);
						this.optionNames.splice(index, 1);
					})
					
					updateProxyConf.options = options
					updateProxyConf.nameList = nameList
					updateProxyConf.configList = configList
					// 重载
					this.reload();
				} else {
					this.optionNames.push(...nameList);
					// 存储记录以备后续使用
					this.proxy_config[type].push(res);
					// 数据同步
					syncWriteConfig(this);
				}
				
				return updateProxyConf || res;
			}
			
		}
		return {
			errMsg
		};
	}
	
	// 移除代理连接
	remove(type, name) {
		// 获取之前的数据
		let proxyConf = this.proxy_config[type].find(info => info.name === name);
		if (!proxyConf) return {errMsg: '需要删除的代理不存在'};
		
		let index = this.proxy_config[type].findIndex(info => info.name === name);
		// 移除代理配置
		this.proxy_config[type].splice(index, 1);
		
		// 移除老数据的代理名称
		proxyConf.nameList.forEach(name => {
			let index = this.optionNames.findIndex(itemName => itemName === name);
			this.optionNames.splice(index, 1);
		});
		// 重新加载
		return this.reload();
	}
	
	// 更新代理连接
	update(type, name, options) {
		// 获取之前的数据
		let updateProxyConf = this.proxy_config[type].find(info => info.name === name);
		if (!updateProxyConf) return {errMsg: '需要更新的代理不存在'}
		// 对应代理类型规则
		return this.add(type, name, options, updateProxyConf)
	}
	
	// 启动代理连接
	start(type, name) {
		// 检查是否关闭当前客户端 还是 关闭指定的代理
		if (type) {
			this.proxy_config[type].forEach(function (info) {
				if (info.name === name || !name) info.isUse = true;
			})
		} else {
			this.isUse = true;
		}
		return this.startProcess();
	}
	
	// 关闭代理连接
	stop(type, name) {
		// 检查是否关闭当前客户端 还是 关闭指定的代理
		if (type) {
			this.proxy_config[type].forEach(function (info) {
				if (info.name === name || !name) info.isUse = false;
			})
		} else {
			this.isUse = false;
		}
		
		if (type) {
			// 重新加载
			this.reload()
		} else {
			// 数据同步
			syncWriteConfig(this, true);
			// 关闭进程
			this.closeProcess();
		}
		return this;
	}
	
	// 启动进程
	startProcess() {
		// 数据同步
		const isPass = syncWriteConfig(this, true);
		// 获取进程对象
		let process = getProcess('client', this);
		// 启动
		if (isPass && !process) {
			process = createProcess('client', this)
			// 输出监听
				.watchStart(info => {
					this.eventMap.start.forEach(fn => fn(this));
				})
				.watchInfo(info => {
					this.eventMap.info.forEach(fn => fn(info));
				})
				.watchError(info => {
					this.eventMap.error.forEach(fn => fn(info));
				})
				.watchClose(info => {
					this.eventMap.close.forEach(fn => fn(info));
					// 销毁进程对象
					delete this.process;
				})
			
		}
		
		// 检查进程是否启动
		if (process && !process.spawn) {
			process.start();
		}
		
		this.process = process;
		return this;
	}
	
	// 关闭进程
	closeProcess() {
		// 关闭进程
		if (this.process) this.process.destroy();
		return this;
	}
	
	watchStart(fn) {
		if (this.process && this.process.isStart) {
			fn(this.process)
		} else {
			// 事件记录
			this.eventMap.start.push(fn);
		}
		return this;
	}
	
	// 监听信息输出
	watchInfo(fn) {
		// 事件记录
		this.eventMap.info.push(fn);
		return this;
	}
	
	// 监听错误输出
	watchError(fn) {
		// 事件记录
		this.eventMap.error.push(fn);
		return this;
	}
	
	// 监听进程关闭
	watchClose(fn) {
		// 事件记录
		this.eventMap.close.push(fn);
		return this;
	}
	
	// 客户端重新加载
	async reload() {
		// 此功能 两种实现方式 1、需要开放web管理服务 ，通过接口进行重载  2、直接关闭进程再重启
		const option = this.common.configList[0];
		// 检查是否开启web管理
		if (option.admin_port) {
			// 数据同步
			const {iniText} = syncWriteConfig(this, true);
			// 配置重新上传
			await clientApi({
				interface: 'upload',
				user: option.admin_user,
				pwd: option.admin_pwd,
				host: option.admin_addr,
				port: option.admin_port,
				options: {
					body: iniText,
					method: 'PUT'
				}
			});
			
			// 通过接口进行重新加载
			await clientApi({
				interface: 'reload',
				user: option.admin_user,
				pwd: option.admin_pwd,
				host: option.admin_addr,
				port: option.admin_port
			})
			
			return this;
		}
		// 关闭进程
		this.closeProcess();
		// 启动进程
		return this.startProcess();
	}
}

// 序列化代理配置
function serializeProxyConfig(proxyInfo, proxyTypeinfos) {
	// 获取配置中对应代理信息
	const {configList: [proxyConfig], ports, nameList = [], name: proxyConfigName} = proxyInfo || {};
	// 获取接口数据中对应的代理状态信息
	const $proxyInfoList = proxyTypeinfos[proxyConfig.type];
	
	// 查看是否范围类型
	if (proxyConfig.range) {
		nameList.shift();
		ports.forEach(function (v, i) {
			nameList.push(proxyConfigName + '_' + i)
		})
	}
	
	// 匹配接口中对应代理信息
	const proxyInfoList = nameList.map(function (proxyName) {
		return $proxyInfoList.find(_proxyInfo => _proxyInfo.name === proxyName) || {};
	})
	
	// 利用配置中信息匹配具体代理信息
	return {
		name: proxyConfigName,
		...proxyConfig,
		proxyInfoList,
	}
}

// 获取代理结果(可以预知代理启动情况)
async function proxyInfo(clientName, proxyType, proxyName) {
	const Client = clientName instanceof client ? clientName : clientData.container.find(client => client.name == clientName);
	if (Client) {
		const option = Client.common.configList[0];
		// 通过接口进行重新加载
		const requestRes = await clientApi({
			interface: 'status',
			user: option.admin_user,
			pwd: option.admin_pwd,
			host: option.admin_addr,
			port: option.admin_port
		});
		
		if (requestRes.response.statusCode == 200) {
			const proxyTypeinfos = requestRes.info;
			
			if (proxyType) {
				const proxyList = Client.proxy_config[proxyType] || [{configList: [{}]}];
				// 根据参数返回配置信息
				if (proxyName) {
					return serializeProxyConfig(proxyList.find(info => info.name === proxyName), proxyTypeinfos)
				}
				return proxyList.map(proxyInfo => {
					return serializeProxyConfig(proxyInfo, proxyTypeinfos)
				})
			}
			
			return Object.keys(Client.proxy_config).reduce(function (map, proxyType) {
				const proxyList = Client.proxy_config[proxyType] || [{configList: [{}]}];
				map[proxyType] = proxyList.map(proxyInfo => {
					return serializeProxyConfig(proxyInfo, proxyTypeinfos)
				})
				return map;
			}, {})
		}
	}
}

module.exports = {
	// 国际化语言包
	i18n,
	// 客户端数据
	clientData,
	// 服务器公共配置规则
	commonRules,
	// 代理规则
	proxyTypeRules,
	// 配置读取
	readConfig,
	// 获取代理结果
	proxyInfo,
	// 客户端移除
	removeClient,
	// 客户端实例回复
	clientRecover,
	// 客户端创建
	createClient(...args) {
		return new client(...args);
	}
}